package com.example.mqttdevicesimulator;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.alibaba.fastjson.JSON;
import com.example.mqttdevicesimulator.jsonformatinfo.ControlInfo;
import com.example.mqttdevicesimulator.jsonformatinfo.DeviceStatusInfo;
import com.example.mqttdevicesimulator.jsonformatinfo.RegisterInfo;
import com.example.mqttdevicesimulator.jsonformatinfo.RoomBindInfo;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.util.Arrays;

import static com.example.mqttdevicesimulator.UtilConstants.DeviceStatus;
import static com.example.mqttdevicesimulator.UtilConstants.Qos;
import static com.example.mqttdevicesimulator.UtilConstants.Topic;

/**
 * Light device simulator. Use phone's flash light.
 */
public class LightActivity extends AppCompatActivity implements DeviceActivity {

    private CameraManager mCameraManager;
    private String mCameraId;
    private Button connectBtn;
    private Button switchBtn;
    private EditText brokerAddressEt;
    private ImageView lightIv;
    private Boolean isDeviceOn;

    private String deviceInfo;
    private RegisterInfo registerInfo;

    //    private String ANDROID_ID;
    private static final String TAG = "LightActivity";

    private String roomId;

    private ClientOperation clientOperation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_light);

        Intent intent = getIntent();
        deviceInfo = intent.getStringExtra("device_info");
        registerInfo = JSON.parseObject(deviceInfo, RegisterInfo.class);

        initUI();
        initDevice();

    }

    @Override
    public void initUI() {
        connectBtn = findViewById(R.id.light_connect_btn);
        switchBtn = findViewById(R.id.light_switch_btn);
        brokerAddressEt = findViewById(R.id.broker_address_et);
        lightIv = findViewById(R.id.light_iv);

        switchBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    if (isDeviceOn) {
                        Log.i(TAG, "onClick: going to switch off");
                        turnOffDevice();
                        isDeviceOn = false;
                    } else {
                        Log.i(TAG, "onClick: going to switch on");
                        turnOnDevice();
                        isDeviceOn = true;
                    }
                } catch (Exception e) {
                    Log.e(TAG, "onClick: switch fail", e);
//                    e.printStackTrace();
                }
            }
        });

        connectBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startMqttService();
            }
        });
    }

    /**
     * Initiate Camera.
     */
    private void initDevice() {
        isDeviceOn = false;
        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            mCameraId = mCameraManager.getCameraIdList()[0];
        } catch (CameraAccessException e) {
            Log.e(TAG, "initDevice: ", e);
//            e.printStackTrace();
        }
        checkFlash();
    }

    // TODO System.exit ?
    /**
     * Check whether device supports flashlight.
     */
    private void checkFlash() {
        boolean isFlashAvailable = getApplicationContext().getPackageManager()
                .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);

        if (!isFlashAvailable) {

            AlertDialog alert = new AlertDialog.Builder(this)
                    .create();
            alert.setTitle("Error !!");
            alert.setMessage("Your device doesn't support flash light!");
            alert.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // closing the application
                    finish();
                    System.exit(0);
                }
            });
            alert.show();
        }
    }

    /**
     * Turn on the flash light, change UI.
     */
    private void turnOnDevice() {
        try {

            mCameraManager.setTorchMode(mCameraId, true);
            Log.i(TAG, "turnOnDevice: turned on");
            isDeviceOn = true;

            this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    lightIv.setImageResource(R.drawable.light_on);
                    switchBtn.setText("Switch Off");
                }
            });

            if (client != null && client.isConnected() && roomId != null) {
                clientOperation.doPublish(Topic.roomFeedback(roomId), generateDeviceStatusMqttMsg(DeviceStatus.ON));
            }


        } catch (Exception e) {
            Log.e(TAG, "turnOnDevice: fail", e);
        }
    }

    /**
     * Turn off the flash light, change UI.
     */
    private void turnOffDevice() {
        try {

            mCameraManager.setTorchMode(mCameraId, false);
            Log.i(TAG, "turnOffDevice: turned off");
            isDeviceOn = false;

            this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    lightIv.setImageResource(R.drawable.light_off);
                    switchBtn.setText("Switch On");
                }
            });

            if (client != null && client.isConnected() && roomId != null) {
                clientOperation.doPublish(Topic.roomFeedback(roomId), generateDeviceStatusMqttMsg(DeviceStatus.OFF));
            }

        } catch (Exception e) {
            Log.e(TAG, "turnOffFlashLight: fail", e);
        }
    }

    private MqttAsyncClient client;
    private String broker;


    @Override
    public void initClient() throws MqttException {

        clientOperation = new ClientOperation(LightActivity.this, broker);

        client = clientOperation.createClient();

        client.setCallback(new MqttCallback() {
            @Override
            public void connectionLost(Throwable cause) {
                Log.w(TAG, "connectionLost: start reconnection", cause);
                clientOperation.doConnect();
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                Log.i(TAG, "messageArrived: " + message.toString());
                if (topic.equals(Topic.ASSIGN_ROOM)) {    // assign the device to a room
                    RoomBindInfo roomBindInfo = JSON.parseObject(message.toString(), RoomBindInfo.class);
                    if (registerInfo.getDevice_id().equals(roomBindInfo.getDevice_id())) {
                        roomId = roomBindInfo.getRoom_id();
                        registerRoom();
                        if (isDeviceOn) {
                            clientOperation.doPublish(Topic.roomFeedback(roomId), generateDeviceStatusMqttMsg(DeviceStatus.ON));
                        } else {
                            clientOperation.doPublish(Topic.roomFeedback(roomId), generateDeviceStatusMqttMsg(DeviceStatus.OFF));
                        }
                    }
                } else if (roomId != null) {
                    if (topic.equals(roomId)) {  // get controlled
                        ControlInfo controlInfo = JSON.parseObject(message.toString(), ControlInfo.class);
                        if (registerInfo.getDevice_id().equals(controlInfo.getDevice_id())) {
                            switch (controlInfo.getCommand().toLowerCase()) {
                                case DeviceStatus.ON:
                                    turnOnDevice();
                                    break;
                                case DeviceStatus.OFF:
                                    turnOffDevice();
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                    if (topic.equals(Topic.roomTouchPad(roomId))) {
                        if (isDeviceOn) {
                            clientOperation.doPublish(Topic.roomFeedback(roomId), generateDeviceStatusMqttMsg(DeviceStatus.ON));
                        } else {
                            clientOperation.doPublish(Topic.roomFeedback(roomId), generateDeviceStatusMqttMsg(DeviceStatus.OFF));
                        }
                    }
                }
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                Log.i(TAG, "Delivery complete callback: Publish Completed " + Arrays.toString(token.getTopics()));
            }
        });
    }


    @Override
    public void registerDevice() {
        MqttMessage message = new MqttMessage(deviceInfo.getBytes());
        message.setQos(Qos.QOS2);

        registerAssignment();
        clientOperation.doPublish(Topic.REGISTER, message);

    }

    @Override
    public void registerRoom() {
        clientOperation.doSubscribe(roomId, Qos.QOS2);
        clientOperation.doSubscribe(Topic.roomTouchPad(roomId), Qos.QOS2);
    }

    @Override
    public void registerAssignment() {
        clientOperation.doSubscribe(Topic.ASSIGN_ROOM, Qos.QOS2);
    }

    @Override
    public void startMqttService() {

        // check new broker input.
        String curInput = brokerAddressEt.getText().toString();
        if (broker == null || !broker.equals(curInput)) {
            broker = curInput;
            client = null; // to start a new client
        }

        if (client == null) {
            try {
                initClient();
            } catch (MqttException e) {
                Log.e(TAG, "startMqttService: fail to create client", e);
            }
        }

        if (!client.isConnected()) {
            //reconnect for those initialized client instance but connection failed.
            clientOperation.doConnect();

            // wait until connector callback invoke.

            synchronized (clientOperation.waiter) {
                try {
                    Log.i(TAG, "wait in connection");

                    // wait for at most maxTTW
                    clientOperation.waiter.wait(clientOperation.maxTTW);
                } catch (InterruptedException e) {
                    Log.e(TAG, "timed out", e);
                }
            }
            if (client.isConnected()) {

                Toast.makeText(LightActivity.this, "Connected to " + broker,
                        Toast.LENGTH_SHORT).show();
                registerDevice();
            } else {

                Toast.makeText(LightActivity.this, "Connect failed",
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    private MqttMessage generateDeviceStatusMqttMsg(String status) {
        DeviceStatusInfo deviceStatusInfo = new DeviceStatusInfo();
        deviceStatusInfo.setDevice_id(registerInfo.getDevice_id());
        deviceStatusInfo.setDevice_status(status);
        deviceStatusInfo.setDevice_name(registerInfo.getDevice_name());
        deviceStatusInfo.setDevice_type(registerInfo.getDevice_type());
//        deviceStatusInfo.setDevice_room_id(room_id);
        String statusInfo = JSON.toJSONString(deviceStatusInfo);
        return new MqttMessage(statusInfo.getBytes());
    }

    /**
     * Turn off the light and disconnect and close the MQTT client.
     */
    @Override
    public void onDestroy() {

        if (isDeviceOn) {
            turnOffDevice();
        }
        if (client != null) {
            if (client.isConnected()) {
                try {
                    client.disconnect();
                    client.close();
                } catch (MqttException e) {
                    Log.e(TAG, "onDestroy: mqtt fail to disconnect", e);
                }
            }
        }

        super.onDestroy();
    }
}

